﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using GPSCrowdSource.Algorithms;
namespace GPSCrowdSource.DataStructures {
  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>
/// Manages a polar coordinate in radians.
/// </summary>
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public class GeoPosition {

  #region Protected Members
  
  //Unique id for this gps position.
  protected String myUID = Guid.NewGuid().ToString();

  #endregion

  #region Properties

  /// <summary>
  /// Get/Set the latitude in decimal degrees
  /// </summary>
  public float Latitude { get; set; }
    
  /// <summary>
  /// Get/Set the longitude in decimal degrees
  /// </summary>
  public float Longitude { get; set; }

  /// <summary>
  /// Get/Set the altitude in feet from surface WGS84
  /// </summary>
  public float Altitude { get; set; }

  /// <summary>
  /// Unique identifier for this position value.
  /// </summary>
  public String UID { get { return myUID; } }

  /// <summary>
  /// Returns the timestamp of this GPS position record.
  /// </summary>
  public DateTime Time { get; set; }

  /// <summary>
  /// Request the current time of day relative to the current day.
  /// </summary>
  public TimeSpan DayTime {
    get {
      return new TimeSpan(Time.Hour,Time.Minute,0);
      }
    }

  #endregion

  #region Constructors

  /// <summary>
  /// Copy constructor (makes an EXACT duplicate)
  /// </summary>
  public GeoPosition(GeoPosition inOther) {
    Latitude = inOther.Latitude;
    Longitude =inOther.Longitude;
    Altitude = inOther.Altitude;
    Time = inOther.Time;
    myUID = inOther.myUID;
    }

  /// <summary>
  /// Create a position at 0,0 on the earths surface.
  /// </summary>
  public GeoPosition() : this(DateTime.UtcNow,0,0,0) {}
  
  /// <summary>
  /// Create a position from a vector that is assumed to
  /// be in polar coordinates (x = longitude, y = latitude, z =altitude)
  /// </summary>
  /// <param name="inPolar">If true the vector is assumed to be in polar coordinates</param>
  /// <param name="inV">The vector of data in either ECEF or polar</param>
  public GeoPosition(Vector3 inV, bool inPolar) : this(DateTime.UtcNow,0,0,0) {
    
    if(!inPolar)
      inV = GeoUtils.ToVectorLatLon(inV.X,inV.Y,inV.Z);

    Latitude = inV.Y;
    Longitude = inV.X;
    Altitude = inV.Z;
    }

  /// <summary>
  /// Create a location coordinate.
  /// </summary>
  /// <param name="inTime">The timestamp of the GPS position</param>
  /// <param name="alt">Altitude from earths surface in feet</param>
  /// <param name="lat">Latitude in decimal degrees</param>
  /// <param name="lon">Longitude in decimal degrees</param>
  public GeoPosition(DateTime inTime, float lat, float lon,float alt) {
    Altitude = alt;
    Latitude = lat;
    Longitude = lon;
    Time = inTime;
    }

  /// <summary>
  /// Create a location coordinate.
  /// </summary>
  /// <param name="inTime">The timestamp of the GPS position</param>
  /// <param name="alt">Altitude from earths surface in feet</param>
  /// <param name="lat">Latitude in decimal degrees</param>
  /// <param name="lon">Longitude in decimal degrees</param>
  public GeoPosition(DateTime inTime, float lat, float lon) : this(inTime,lat,lon,0) {}

  /// <summary>
  /// Create a location coordinate.
  /// </summary>
  /// <param name="inTime">The timestamp of the GPS position</param>
  /// <param name="alt">Altitude from earths surface in feet</param>
  /// <param name="lat">Latitude in decimal degrees</param>
  /// <param name="lon">Longitude in decimal degrees</param>
  public GeoPosition(float lat, float lon, float alt) : this(DateTime.UtcNow,lat,lon,alt) {
    }

  /// <summary>
  /// Create a location coordinate.
  /// </summary>
  /// <param name="inTime">The timestamp of the GPS position</param>
  /// <param name="alt">Altitude from earths surface in feet</param>
  /// <param name="lat">Latitude in decimal degrees</param>
  /// <param name="lon">Longitude in decimal degrees</param>
  public GeoPosition(float lat, float lon) : this(DateTime.UtcNow,lat,lon,0) {
    }

  
  /// <summary>
  /// Create a geo position from a formatted text line which is
  /// lat,lon,alt,time.
  /// 
  /// Lat/Lon are decimal degrees
  /// alt is in feet,
  /// time is milliseconds since Unix Epoch Midnight Jan 1 1970
  /// </summary>
  public GeoPosition(String ln) : this() {
    String[] parts = ln.Split(new char[] {','});
    float lat = 0;
    float lon = 0;
    float alt = 0;
    int time = 0;

    if(parts.Length > 0)
      float.TryParse(parts[0],out lat);
    if(parts.Length > 1)
      float.TryParse(parts[1],out lon);
    if(parts.Length > 2)
      float.TryParse(parts[2],out alt);
    if(parts.Length > 3)
      int.TryParse(parts[3],out time);

    Time = new DateTime(1970,1,1,0,0,0,0,DateTimeKind.Utc).Add(new TimeSpan(0,0,0,0,(int)time));
    Latitude = lat;
    Longitude = lon; 
    Altitude = alt;
    }

  #endregion
  
  /// <summary>
  /// Conver the polar value to a vector of 3 values being the x,y,z
  /// respectively in ECEF reference.
  /// </summary>
  /// <returns>float[0] = x, float[1] = y, float[2] = z in ECEF cartesian coordinates</returns>
  public Vector3 ToVectorECEF() {    // DEBUG param
    return GeoUtils.ToVectorECEF(Latitude,Longitude,Altitude);
    }
    
  /// <summary>
  /// Convert to a vector structure where
  /// lon,lat,alt are the x,y,z.
  /// </summary>
  public Vector3 ToVector() {
    return new Vector3(Longitude,Latitude,Altitude);
    }

  /// <summary>
  /// Add the vector to this position.
  /// Assumes the vector is lat/lon/alt format.
  /// </summary>
  public GeoPosition Add(Vector3 v) {
    GeoPosition gp = new GeoPosition(this);
    gp.Longitude = Latitude+v.X;
    gp.Latitude = Latitude+v.Y;
    gp.Altitude = Latitude+v.Z;
    return gp;
    }

  #region Object Overries

  public override bool Equals(object obj) {
    if(obj == null) return false;
    if(ReferenceEquals(this,obj)) return true;

    GeoPosition other = (GeoPosition)obj;
    return other.Altitude == Altitude &&
            other.Latitude == Latitude &&
            other.Longitude == Longitude;
    }

  public override int GetHashCode() {
    return ToString().GetHashCode();
    }

  public override string ToString() {
    return "" + Latitude + "," + Longitude + "," + Altitude + "," + Time;
    }

  #endregion

  #region Operator Overloads

  public static bool operator==(GeoPosition a, GeoPosition b) {
    if( ((Object)a) == null && ((Object)b) == null) return true;
    else if( ((Object)a) == null || ((Object)b) == null) return false;
    return a.Equals(b);
    }

  public static bool operator!=(GeoPosition a, GeoPosition b) {
    return !(a == b);
    }

  /// <summary>
  /// Produce a vector between the to locations where
  ///  v = a-b
  /// </summary>
  public static Vector3 operator-(GeoPosition a, GeoPosition b) {
    return new Vector3(a.Longitude-b.Longitude,
                       a.Latitude-b.Latitude,
                       a.Altitude-b.Altitude);
    }

  #endregion

  }
  

}
